﻿using System;
namespace JoinBox.Basal;

using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using GeneralEvents = Action<object, object>;

/// <summary>
/// 监控文件变化
/// </summary>
public class FileListener
{
    [Flags]
    public enum Modes : byte
    {
        Created = 1,
        Changed = 2,
        Deleted = 4,
        Renamed = 8,
        All = Created | Changed | Deleted | Renamed
    }

    /// <summary>
    /// 限时防止重复触发(默认值一秒)
    /// </summary>
    public int LimitedTime = 1000;
    /// <summary>
    /// 监控文件的词典:(key路径+后缀,value监控服务)
    /// </summary>
    readonly Dictionary<string, FileSystemWatcherEx> _fileListeners = new();
    static DateTime _timeBak;
    Dictionary<string, GeneralEvents> _dict;

    /// <summary>
    /// 监控文件变化
    /// </summary>
    /// <param name="paths">要监控的路径们</param>
    /// <param name="dict">
    /// <see cref="string"/>不含*号的后缀名(必须小写),例如".cui|.cuix",<br/>
    /// <see cref="Action"/>执行方法(文件修改事件)
    /// </param>
    FileListener(string[] paths, Dictionary<string, GeneralEvents> dict)
    {
        _dict = dict;

        // 切割任务
        // 处理后缀名的|号切割,数据是
        var keys = _dict.Keys.ToArray();// 克隆副本
        for (int ii = 0; ii < keys.Length; ii++)
        {
            var key = keys[ii];
            var exts = key.Split('|');
            if (exts.Length < 2)
                continue;

            for (int jj = 0; jj < exts.Length; jj++)
            {
                var ext = exts[jj];

                // 防止多个后缀通过|符越过词典约束同名
                // 后缀(key)含有,而且Action(value)不同,就把Action(value)累加到后面.
                if (!_dict.ContainsKey(ext))
                    _dict.Add(ext, _dict[key]);
                else if (_dict[ext] != _dict[key])
                    _dict[ext] += _dict[key];
            }
            _dict.Remove(key);
        }

        // 添加时间监控
        for (int i = 0; i < paths.Length; i++)
        {
            var path = paths[i];
            if (!_fileListeners.ContainsKey(path))
                _fileListeners.Add(path, new FileSystemWatcherEx(path));
        }
    }

    /// <summary>
    /// 监控文件变化
    /// </summary>
    /// <param name="paths">要监控的路径们,末尾不要斜杠</param>
    /// <param name="dict">
    /// <see cref="string"/>不含*号的后缀名(必须小写),例如".cui|.cuix",<br/>
    /// <see cref="Action"/>执行方法
    /// <code>
    ///     0x01: <see cref="System.IO.FileSystemEventHandler"/>文件修改事件
    ///     0x02: <see cref="System.IO.RenamedEventHandler"/>文件改名事件
    /// </code>
    /// </param>
    /// <param name="modes">应用方式</param>
    public FileListener(string[] paths,
                        Dictionary<string, GeneralEvents> dict,
                        Modes modes) : this(paths, dict)
    {
        // 遍历所有的方法并执行
        // vscode保存会触发三次监控文件,记事本打开只会一次,所以需要间隔
        _timeBak = DateTime.Now;
        foreach (var fsw in _fileListeners.Values)
        {
            if ((modes & Modes.Created) == Modes.Created)
                fsw.Created += FileSystemEventHandler;
            if ((modes & Modes.Changed) == Modes.Changed)
                fsw.Changed += FileSystemEventHandler;
            if ((modes & Modes.Deleted) == Modes.Deleted)
                fsw.Deleted += FileSystemEventHandler;
            if ((modes & Modes.Renamed) == Modes.Renamed)
                fsw.Renamed += RenamedEventHandler;
            fsw.Start();
        }
    }

    /// <summary>
    /// 文件新增/变更/删除/
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void FileSystemEventHandler(object sender, FileSystemEventArgs e)
    {
        var type = e.ChangeType.ToString();
        if (type == "Created")
            Debug.WriteLine("新增:: " + e.FullPath);//+ "; Name:" + e.Name
        else if (type == "Changed")
            Debug.WriteLine("变更:: " + e.FullPath);
        else if (type == "Deleted")
            Debug.WriteLine("删除:: " + e.FullPath);
        // Debug.WriteLine("-----------------" + sender);// IFoxCAD.AutoLoad.FileSystemWatcherEx

        Event(e.FullPath, sender, e);
    }

    /// <summary>
    /// 改名
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void RenamedEventHandler(object sender, RenamedEventArgs e)
    {
        Debug.WriteLine("改名:: " + e.FullPath);
        Event(e.FullPath, sender, e);
    }

    public HashSet<string> TimeFiles = new();

    void Event(string path, object sender, object e)
    {
        var now = DateTime.Now;
        var ts = now - _timeBak;

        // 在这个时间间隔内修改的文件全部加入到哈希容器内
        TimeFiles.Add(path);

        if (ts.TotalMilliseconds < LimitedTime)// 间隔过少时间就跳过
            return;
        _timeBak = DateTime.Now;

        var ext = Path.GetExtension(path).ToLower();
        if (_dict.ContainsKey(ext))
            _dict[ext]?.Invoke(sender, e);// 指定文件

        TimeFiles.Clear();
    }

    /// <summary>
    /// 卸载
    /// </summary>
    public void Uninstall()
    {
        // 立即阻止所有的状态
        LimitedTime = int.MaxValue;

        foreach (var item in _fileListeners.Values)
        {
            item.Stop();
            item.Dispose();// 事件在内部移除了
        }

        _fileListeners.Clear();
    }
}


/// <summary>
/// 监控路径和子目录的文件,最好不要直接利用它,因为有时间间隔问题
/// </summary>
public class FileSystemWatcherEx : FileSystemWatcher
{
    /// <summary>
    /// 监控路径和子目录的文件
    /// </summary>
    /// <param name="fileOrPath">路径</param>
    /// <param name="filter">过滤文件规则,为<see cref="null"/>的时候监控路径的文件夹</param>
    /// <param name="includeSubdirectories">包含子目录</param>
    public FileSystemWatcherEx(string fileOrPath,
                               string? filter = null,
                               bool includeSubdirectories = true)
    {
        if (fileOrPath is null)
            throw new ArgumentNullException(nameof(fileOrPath));

        // 是文件就读取后缀
        if (IsFile(fileOrPath))
        {
            filter = System.IO.Path.GetExtension(fileOrPath);
            fileOrPath = System.IO.Path.GetDirectoryName(fileOrPath);
        }

        // 监控路径
        Path = fileOrPath;

        // 监控文件
        // 默认 "*.*" 不支持使用多个筛选器,例如 "*.txt|*.doc"
        // 支持 win*  *recipe.doc  销售额*200?.xls  MyReport.Doc
        if (filter is not null)
        {
            if (!filter.Contains("*"))
                filter = "*" + filter;
            Filter = filter;
        }

        NotifyFilter = NotifyFilters.FileName
                     | NotifyFilters.DirectoryName
                     | NotifyFilters.Attributes
                     | NotifyFilters.Size
                     | NotifyFilters.LastWrite
                     | NotifyFilters.LastAccess
                     | NotifyFilters.CreationTime
                     | NotifyFilters.Security;

        // 是否应监视指定路径中的子目录
        IncludeSubdirectories = includeSubdirectories;
    }


    /// <summary>
    /// 判断是文件还是目录(目录包括磁盘)
    /// </summary>
    /// <param name="filePath">路径</param>
    /// <returns>返回true为一个文件,返回false为一个目录</returns>
    public static bool IsFile(string filePath)
    {
        var fi = new FileInfo(filePath);
        return (fi.Attributes & FileAttributes.Directory) == 0;
    }

    /// <summary>
    /// 启动文件监控
    /// </summary>
    public void Start()
    {
        EnableRaisingEvents = true;
    }

    /// <summary>
    /// 停止
    /// </summary>
    public void Stop()
    {
        EnableRaisingEvents = false;
    }
}

